# -*- coding: utf-8 -*-
"""
Created on Mon Feb  4 21:48:54 2019
windows7
python3.6.3
Anaconda3->spyder
opencv3.4.1
代码参考《OpenCV3计算机视觉 Python语言实现》（第二版）
@author: Administrator
"""

import cv2
import numpy as np
from os.path import join

print(cv2.__version__)                    # 查看opencv的版本
#######################################################
#训练一个SVM
'''声明训练图像的地址'''
datapath = "images" 
def path(cls,i):
    return "%s/%s%d.jpg"  % (datapath,cls,i+1)
#正负样本
pos, neg = "pos/pos-", "neg/neg-"       # 前面笔误写成了nes/nes-

detect = cv2.xfeatures2d.SIFT_create()  # 用于提取关键点  # 该算法被申请了专利，3.4.2以上的版本不能使用
extract = cv2.xfeatures2d.SIFT_create() # 用来提取特征   # 该算法被申请了专利，3.4.2以上的版本不能使用
"""Windows下使用win+R键运行cmd，并输入pip uninstall opencv-python来卸载OPENCV，重新安装旧版的"""

flann_params = dict(algorithm = 1, trees = 5)
matcher = cv2.FlannBasedMatcher(flann_params, {})

"""BOW训练器"""
bow_kmeans_trainer = cv2.BOWKMeansTrainer(40)
extract_bow = cv2.BOWImgDescriptorExtractor(extract, matcher)

"""从图像中提取SIFT特征"""
def extract_sift(fn):
    im = cv2.imread(fn,0)
    return extract.compute(im, detect.detect(im))[1]

'''每个类读取8张图片做训练样本'''
for i in range(8):
    bow_kmeans_trainer.add(extract_sift(path(pos,i)))
    bow_kmeans_trainer.add(extract_sift(path(neg,i)))

'''.cluster()是调用k_means分类并返回词汇，为BOWImgDescriptorExtractor指定返回的词汇，以便它能从测试图像中提取描述符'''
voc = bow_kmeans_trainer.cluster()
extract_bow.setVocabulary( voc )

"""对得到的描述符进行计算"""
def bow_features(fn):
    im = cv2.imread(fn,0)
    return extract_bow.compute(im, detect.detect(im))

'''创建训练数据和标签数组'''
traindata, trainlabels = [],[]
for i in range(20):       #使用了20个样本，具体数据根据自己所给的样本决定
    traindata.extend(bow_features(path(pos, i)))
    trainlabels.append(1)
    traindata.extend(bow_features(path(neg, i)))
    trainlabels.append(-1)

'''创建SVM实例'''
svm = cv2.ml.SVM_create()
'''将训练数据和训练数组放到Numpy数组中训练'''
svm.train(np.array(traindata), cv2.ml.ROW_SAMPLE, np.array(trainlabels))
#上面的属于训练SVM
#################################################################

'''对图片进行预测'''
def predict(fn):
    f = bow_features(fn);  
    p = svm.predict(f)
    print(fn, "\t", p[1][0][0])
    return p

'''要用来进行测试的图像的路径'''
car, notcar = "images/predict/car.jpg", "images/predict/notcar.jpg"
car_img = cv2.imread(car)
notcar_img = cv2.imread(notcar)
car_predict = predict(car)         #传给训练好的SVM进行预测
not_car_predict = predict(notcar)  #预测

font = cv2.FONT_HERSHEY_SIMPLEX

#在图像上显示文字
if (car_predict[1][0][0] == 1.0):
    cv2.putText(car_img,'Car Detected',(10,30), font, 1,(0,255,0),2,cv2.LINE_AA)

if (not_car_predict[1][0][0] == -1.0):
    cv2.putText(notcar_img,'Car Not Detected',(10,30), font, 1,(0,0, 255),2,cv2.LINE_AA)
    #最后图片上没有输出'Car Not Detected'这段文本是因为传入的图没有被判断为Not car这一类

cv2.imshow('BOW + SVM Success', car_img)
cv2.imshow('BOW + SVM Failure', notcar_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

"""
报过的错
1.error: (-215) !_descriptors.empty() in function cv::BOWTrainer::add
    是训练样本的地址没填写正确
2.error: (-215) samples.cols == var_count && samples.type() == 5 in function cv::ml::SVMImpl::predict
    原因是要预测即用来测试的图的地址填写错误
"""